﻿package eu.szemraj.utils.color {
   import flash.filters.ColorMatrixFilter;

   public class ColorMatrix {

      public static const COLOR_DEFICIENCY_TYPES:Array = ['Protanopia',
															'Protanomaly',
															'Deuteranopia',
															'Deuteranomaly',
															'Tritanopia',
															'Tritanomaly',
															'Achromatopsia',
															'Achromatomaly'];
															
      private static const LUMA_R:Number = 0.212671;
      private static const LUMA_G:Number = 0.71516;
      private static const LUMA_B:Number = 0.072169;

      private static const LUMA_R2:Number = 0.3086;
      private static const LUMA_G2:Number = 0.6094;
      private static const LUMA_B2:Number = 0.0820;
      private static const ONETHIRD:Number = 1 / 3;
      private static const IDENTITY:Array = [1,0,0,0,0,
											 0,1,0,0,0,
											 0,0,1,0,0,
											 0,0,0,1,0];
      private static const RAD:Number = Math.PI / 180;
      public var matrix:Array;
      private var preHue:ColorMatrix;
      private var postHue:ColorMatrix;
      private var hueInitialized:Boolean;

      public function ColorMatrix( mat:Array = null ) {
			
		 if (mat is Array ) {
            matrix = mat.concat( );
         } else {
            reset( );
         }
      }

      public function reset():void {
         matrix = IDENTITY.concat( );
      }

      public function clone():ColorMatrix {
         return new ColorMatrix( matrix );
      }

      public function invert():void {
         concat( [-1 ,  0,  0, 0, 255,
					  0 , -1,  0, 0, 255,
					  0 ,  0, -1, 0, 255,
					  0,   0,  0, 1,   0] );
      }

      public function adjustSaturation( s:Number ):void {
            
         var sInv:Number;
         var irlum:Number;
         var iglum:Number;
         var iblum:Number;
            
         sInv = (1 - s);
         irlum = (sInv * LUMA_R);
         iglum = (sInv * LUMA_G);
         iblum = (sInv * LUMA_B);
            
         concat( [(irlum + s), iglum, iblum, 0, 0, 
            		irlum, (iglum + s), iblum, 0, 0, 
            		irlum, iglum, (iblum + s), 0, 0, 
            		0, 0, 0, 1, 0] );
      }

      public function adjustContrast( r:Number, g:Number = NaN, b:Number = NaN ):void {
         if (isNaN( g )) g = r;
         if (isNaN( b )) b = r;
         r += 1;
         g += 1;
         b += 1;
			
         concat( [r, 0, 0, 0, (128 * (1 - r)), 
					0, g, 0, 0, (128 * (1 - g)), 
					0, 0, b, 0, (128 * (1 - b)), 
					0, 0, 0, 1, 0] );
      }

      public function adjustBrightness(r:Number, g:Number = NaN, b:Number = NaN):void {
         if (isNaN( g )) g = r;
         if (isNaN( b )) b = r;
         concat( [1, 0, 0, 0, r, 
            		0, 1, 0, 0, g, 
            		0, 0, 1, 0, b, 
            		0, 0, 0, 1, 0] );
      }

      public function adjustHue( degrees:Number ):void {
         degrees *= RAD;
         var cos:Number = Math.cos( degrees );
         var sin:Number = Math.sin( degrees );
         concat( [((LUMA_R + (cos * (1 - LUMA_R))) + (sin * -(LUMA_R))), ((LUMA_G + (cos * -(LUMA_G))) + (sin * -(LUMA_G))), ((LUMA_B + (cos * -(LUMA_B))) + (sin * (1 - LUMA_B))), 0, 0, 
            		((LUMA_R + (cos * -(LUMA_R))) + (sin * 0.143)), ((LUMA_G + (cos * (1 - LUMA_G))) + (sin * 0.14)), ((LUMA_B + (cos * -(LUMA_B))) + (sin * -0.283)), 0, 0, 
            		((LUMA_R + (cos * -(LUMA_R))) + (sin * -((1 - LUMA_R)))), ((LUMA_G + (cos * -(LUMA_G))) + (sin * LUMA_G)), ((LUMA_B + (cos * (1 - LUMA_B))) + (sin * LUMA_B)), 0, 0, 
            		0, 0, 0, 1, 0] );
      }

      public function rotateHue( degrees:Number ):void {
         initHue( );
			
         concat( preHue.matrix );
         rotateBlue( degrees );
         concat( postHue.matrix );
      }

      public function luminance2Alpha():void {
         concat( [0, 0, 0, 0, 255, 
            		0, 0, 0, 0, 255, 
            		0, 0, 0, 0, 255, 
            		LUMA_R, LUMA_G, LUMA_B, 0, 0] );
      }

      public function adjustAlphaContrast(amount:Number):void {
         amount += 1;
         concat( [1, 0, 0, 0, 0, 
            		0, 1, 0, 0, 0, 
            		0, 0, 1, 0, 0, 
            		0, 0, 0, amount, (128 * (1 - amount))] );
      }

      public function colorize(rgb:int, amount:Number = 1):void {
         var r:Number;
         var g:Number;
         var b:Number;
         var inv_amount:Number;
            
         r = (((rgb >> 16) & 0xFF) / 0xFF);
         g = (((rgb >> 8) & 0xFF) / 0xFF);
         b = ((rgb & 0xFF) / 0xFF);
         inv_amount = (1 - amount);
            
         concat( [(inv_amount + ((amount * r) * LUMA_R)), ((amount * r) * LUMA_G), ((amount * r) * LUMA_B), 0, 0, 
            		((amount * g) * LUMA_R), (inv_amount + ((amount * g) * LUMA_G)), ((amount * g) * LUMA_B), 0, 0, 
            		((amount * b) * LUMA_R), ((amount * b) * LUMA_G), (inv_amount + ((amount * b) * LUMA_B)), 0, 0, 
            		0, 0, 0, 1, 0] );
      }

      public function setChannels( r:int = 1, g:int = 2, b:int = 4, a:int = 8):void {
         var rf:Number = ((((((r & 1) == 1)) ? 1 : 0 + Number(((r & 2) == 2)) ? 1 : 0) + Number(((r & 4) == 4)) ? 1 : 0) + Number(((r & 8) == 8)) ? 1 : 0);
         if (rf > 0) {
            rf = (1 / rf);
         }
         ;
         var gf:Number = ((((((g & 1) == 1)) ? 1 : 0 + Number(((g & 2) == 2)) ? 1 : 0) + Number(((g & 4) == 4)) ? 1 : 0) + Number(((g & 8) == 8)) ? 1 : 0);
         if (gf > 0) {
            gf = (1 / gf);
         }
         ;
         var bf:Number = ((((((b & 1) == 1)) ? 1 : 0 + Number(((b & 2) == 2)) ? 1 : 0) + Number(((b & 4) == 4)) ? 1 : 0) + Number(((b & 8) == 8)) ? 1 : 0);
         if (bf > 0) {
            bf = (1 / bf);
         }
         ;
         var af:Number = ((((((a & 1) == 1)) ? 1 : 0 + Number(((a & 2) == 2)) ? 1 : 0) + Number(((a & 4) == 4)) ? 1 : 0) + Number(((a & 8) == 8)) ? 1 : 0);
         if (af > 0) {
            af = (1 / af);
         }
         ;
         concat( [(((r & 1) == 1)) ? rf : 0, (((r & 2) == 2)) ? rf : 0, (((r & 4) == 4)) ? rf : 0, (((r & 8) == 8)) ? rf : 0, 0, (((g & 1) == 1)) ? gf : 0, (((g & 2) == 2)) ? gf : 0, (((g & 4) == 4)) ? gf : 0, (((g & 8) == 8)) ? gf : 0, 0, (((b & 1) == 1)) ? bf : 0, (((b & 2) == 2)) ? bf : 0, (((b & 4) == 4)) ? bf : 0, (((b & 8) == 8)) ? bf : 0, 0, (((a & 1) == 1)) ? af : 0, (((a & 2) == 2)) ? af : 0, (((a & 4) == 4)) ? af : 0, (((a & 8) == 8)) ? af : 0, 0] );
      }

      public function blend(mat:ColorMatrix, amount:Number):void {
         var inv_amount:Number = (1 - amount);
         var i:int = 0;
         while (i < 20) {
            matrix[i] = ((inv_amount * Number( matrix[i] )) + (amount * Number( mat.matrix[i] )));
            i++;
         }
         ;
      }

      public function average( r:Number = ONETHIRD, g:Number = ONETHIRD, b:Number = ONETHIRD):void {
         concat( [r, g, b, 0, 0, 
            		r, g, b, 0, 0, 
            		r, g, b, 0, 0, 
            		0, 0, 0, 1, 0] );
      }

      public function threshold(threshold:Number, factor:Number = 256):void {
         concat( [(LUMA_R * factor), (LUMA_G * factor), (LUMA_B * factor), 0, (-(factor) * threshold), 
            		(LUMA_R * factor), (LUMA_G * factor), (LUMA_B * factor), 0, (-(factor) * threshold), 
            		(LUMA_R * factor), (LUMA_G * factor), (LUMA_B * factor), 0, (-(factor) * threshold), 
            		0, 0, 0, 1, 0] );
      }

      public function desaturate():void {
         concat( [LUMA_R, LUMA_G, LUMA_B, 0, 0, 
            		LUMA_R, LUMA_G, LUMA_B, 0, 0, 
            		LUMA_R, LUMA_G, LUMA_B, 0, 0, 
            		0, 0, 0, 1, 0] );
      }

      public function randomize(amount:Number = 1):void {
         var inv_amount:Number = (1 - amount);
         var r1:Number = (inv_amount + (amount * (Math.random( ) - Math.random( ))));
         var g1:Number = (amount * (Math.random( ) - Math.random( )));
         var b1:Number = (amount * (Math.random( ) - Math.random( )));
         var o1:Number = ((amount * 0xFF) * (Math.random( ) - Math.random( )));
         var r2:Number = (amount * (Math.random( ) - Math.random( )));
         var g2:Number = (inv_amount + (amount * (Math.random( ) - Math.random( ))));
         var b2:Number = (amount * (Math.random( ) - Math.random( )));
         var o2:Number = ((amount * 0xFF) * (Math.random( ) - Math.random( )));
         var r3:Number = (amount * (Math.random( ) - Math.random( )));
         var g3:Number = (amount * (Math.random( ) - Math.random( )));
         var b3:Number = (inv_amount + (amount * (Math.random( ) - Math.random( ))));
         var o3:Number = ((amount * 0xFF) * (Math.random( ) - Math.random( )));
           
         concat( [r1, g1, b1, 0, o1, 
            		r2, g2, b2, 0, o2, 
            		r3, g3, b3, 0, o3, 
            		0, 0, 0, 1, 0] );
      }

      public function setMultiplicators( red:Number = 1, green:Number = 1, blue:Number = 1, alpha:Number = 1 ):void {
         var mat:Array = new Array( red, 0, 0, 0, 0, 0, green, 0, 0, 0, 0, 0, blue, 0, 0, 0, 0, 0, alpha, 0 );
			
         concat( mat );
      }

      public function clearChannels( red:Boolean = false, green:Boolean = false, blue:Boolean = false, alpha:Boolean = false ):void {
         if ( red ) {
            matrix[0] = matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0;
         }
         if ( green ) {
            matrix[5] = matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0;
         }
         if ( blue ) {
            matrix[10] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0;
         }
         if ( alpha ) {
            matrix[15] = matrix[16] = matrix[17] = matrix[18] = matrix[19] = 0;
         }
      }

      public function thresholdAlpha( threshold:Number, factor:Number = 256):void {
         concat( [1, 0, 0, 0, 0, 
            		0, 1, 0, 0, 0, 
            		0, 0, 1, 0, 0, 
            		0, 0, 0, factor, (-factor * threshold)] );
      }

      public function averageRGB2Alpha():void {
         concat( [0, 0, 0, 0, 255, 
            		0, 0, 0, 0, 255, 
            		0, 0, 0, 0, 255, 
            		ONETHIRD, ONETHIRD, ONETHIRD, 0, 0] );
      }

      public function invertAlpha():void {
         concat( [1, 0, 0, 0, 0, 
            		0, 1, 0, 0, 0, 
            		0, 0, 1, 0, 0, 
            		0, 0, 0, -1, 255] );
      }

      public function rgb2Alpha( r:Number, g:Number, b:Number ):void {
         concat( [0, 0, 0, 0, 255, 
            		0, 0, 0, 0, 255, 
            		0, 0, 0, 0, 255, 
            		r, g, b, 0, 0] );
      }

      public function setAlpha(alpha:Number):void {
         concat( [1, 0, 0, 0, 0, 
            		0, 1, 0, 0, 0, 
            		0, 0, 1, 0, 0, 
            		0, 0, 0, alpha, 0] );
      }

      public function get filter():ColorMatrixFilter {
         return new ColorMatrixFilter( matrix );
      }

      public function concat( mat:Array ):void {
			
         var temp:Array = [];
         var i:int = 0;
         var x:int, y:int;
         for (y = 0; y < 4 ; y++ ) {
				
            for (x = 0; x < 5 ; x++ ) {
               temp[ int( i + x ) ] = Number( mat[i  ] ) * Number( matrix[x] ) + Number( mat[int( i + 1 )] ) * Number( matrix[int( x + 5 )] ) + Number( mat[int( i + 2 )] ) * Number( matrix[int( x + 10 )] ) + Number( mat[int( i + 3 )] ) * Number( matrix[int( x + 15 )] ) + (x == 4 ? Number( mat[int( i + 4 )] ) : 0);
            }
            i += 5;
         }
			
         matrix = temp;
      }

      public function rotateRed( degrees:Number ):void {
         rotateColor( degrees, 2, 1 ); 
      }

      public function rotateGreen( degrees:Number ):void {
         rotateColor( degrees, 0, 2 ); 
      }

      public function rotateBlue( degrees:Number ):void {
         rotateColor( degrees, 1, 0 ); 
      }

      private function rotateColor( degrees:Number, x:int, y:int ):void {
         degrees *= RAD;
         var mat:Array = IDENTITY.concat( );
         mat[ x + x * 5 ] = mat[ y + y * 5 ] = Math.cos( degrees );
         mat[ y + x * 5 ] = Math.sin( degrees );
         mat[ x + y * 5 ] = -Math.sin( degrees );
         concat( mat );
      }

      public function shearRed( green:Number, blue:Number ):void {
         shearColor( 0, 1, green, 2, blue );
      }

      public function shearGreen( red:Number, blue:Number ):void {
         shearColor( 1, 0, red, 2, blue );
      }

      public function shearBlue( red:Number, green:Number ):void {
         shearColor( 2, 0, red, 1, green );
      }

      private function shearColor( x:int, y1:int, d1:Number, y2:int, d2:Number ):void {
         var mat:Array = IDENTITY.concat( );
         mat[ y1 + x * 5 ] = d1;
         mat[ y2 + x * 5 ] = d2;
         concat( mat );
      }

      public function applyColorDeficiency( type:String ):void {
         // the values of this method are copied from http://www.nofunc.com/Color_Matrix_Library/ 
         switch ( type ) {
            case 'Protanopia':
               concat( [0.567,0.433,0,0,0, 0.558,0.442,0,0,0, 0,0.242,0.758,0,0, 0,0,0,1,0] );
               break;
            case 'Protanomaly':
               concat( [0.817,0.183,0,0,0, 0.333,0.667,0,0,0, 0,0.125,0.875,0,0, 0,0,0,1,0] );
               break;
            case 'Deuteranopia':
               concat( [0.625,0.375,0,0,0, 0.7,0.3,0,0,0, 0,0.3,0.7,0,0, 0,0,0,1,0] );
               break;
            case 'Deuteranomaly':
               concat( [0.8,0.2,0,0,0, 0.258,0.742,0,0,0, 0,0.142,0.858,0,0, 0,0,0,1,0] );
               break;
            case 'Tritanopia':
               concat( [0.95,0.05,0,0,0, 0,0.433,0.567,0,0, 0,0.475,0.525,0,0, 0,0,0,1,0] );
               break;
            case 'Tritanomaly':
               concat( [0.967,0.033,0,0,0, 0,0.733,0.267,0,0, 0,0.183,0.817,0,0, 0,0,0,1,0] );
               break;
            case 'Achromatopsia':
               concat( [0.299,0.587,0.114,0,0, 0.299,0.587,0.114,0,0, 0.299,0.587,0.114,0,0, 0,0,0,1,0] );
               break;
            case 'Achromatomaly':
               concat( [0.618,0.320,0.062,0,0, 0.163,0.775,0.062,0,0, 0.163,0.320,0.516,0,0, 0,0,0,1,0] );
               break;
         }
      }

      public function applyMatrix( rgba:uint ):uint {
         var a:Number = ( rgba >>> 24 ) & 0xff;
         var r:Number = ( rgba >>> 16 ) & 0xff;
         var g:Number = ( rgba >>> 8 ) & 0xff;
         var b:Number = rgba & 0xff;
       		
         var r2:int = 0.5 + r * matrix[0] + g * matrix[1] + b * matrix[2] + a * matrix[3] + matrix[4];
         var g2:int = 0.5 + r * matrix[5] + g * matrix[6] + b * matrix[7] + a * matrix[8] + matrix[9];
         var b2:int = 0.5 + r * matrix[10] + g * matrix[11] + b * matrix[12] + a * matrix[13] + matrix[14];
         var a2:int = 0.5 + r * matrix[15] + g * matrix[16] + b * matrix[17] + a * matrix[18] + matrix[19];
       		
         if ( a2 < 0 ) a2 = 0;
         if ( a2 > 255 ) a2 = 255;
         if ( r2 < 0 ) r2 = 0;
         if ( r2 > 255 ) r2 = 255;
         if ( g2 < 0 ) g2 = 0;
         if ( g2 > 255 ) g2 = 255;
         if ( b2 < 0 ) b2 = 0;
         if ( b2 > 255 ) b2 = 255;
       		
         return a2 << 24 | r2 << 16 | g2 << 8 | b2;
      }

      public function transformVector( values:Array ):void {
         if ( values.length != 4) return;
        	
         var r:Number = values[0] * matrix[0] + values[1] * matrix[1] + values[2] * matrix[2] + values[3] * matrix[3] + matrix[4];
         var g:Number = values[0] * matrix[5] + values[1] * matrix[6] + values[2] * matrix[7] + values[3] * matrix[8] + matrix[9];
         var b:Number = values[0] * matrix[10] + values[1] * matrix[11] + values[2] * matrix[12] + values[3] * matrix[13] + matrix[14];
         var a:Number = values[0] * matrix[15] + values[1] * matrix[16] + values[2] * matrix[17] + values[3] * matrix[18] + matrix[19];
       		
         values[0] = r;
         values[1] = g;
         values[2] = b;
         values[3] = a;
      }

      private function initHue():void {
			
         //var greenRotation:Number = 35.0;
         var greenRotation:Number = 39.182655;

         if (!hueInitialized) {
            hueInitialized = true;
            preHue = new ColorMatrix( );
            preHue.rotateRed( 45 );
            preHue.rotateGreen( -greenRotation );
	
            var lum:Array = [LUMA_R2, LUMA_G2, LUMA_B2, 1.0];
	
            preHue.transformVector( lum );
	
            var red:Number = lum[0] / lum[2];
            var green:Number = lum[1] / lum[2];
	
            preHue.shearBlue( red, green );
	
            postHue = new ColorMatrix( );
            postHue.shearBlue( -red, -green );
            postHue.rotateGreen( greenRotation );
            postHue.rotateRed( -45.0 );
         }
      }
   }
}